package org.bsscheduler;

import java.io.File;
import java.io.IOException;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.SAXException;

/**
 *
 * @author tblackstone
 */
public class EventsDB {

    private static final String eventFilePath = "eventDB" + File.separator;

    private static TreeMap<String, EventList> eventDB = new TreeMap<String, EventList>();

    // Private constructor - Instances Sind Verboten!
    private EventsDB() {
    }


    /**
     *  Important code!!! Loads event XML files into DB on startup, using
     *  loadEvents() method below
     */
    public static void startup() {

        File eventsDirectory = new File(eventFilePath);
        if (!eventsDirectory.isDirectory()) {
            eventsDirectory.mkdir();
        }

        String[] files = eventsDirectory.list(new XMLFileFilter());

        for (String f: files) {
            f = eventsDirectory.getAbsolutePath() + File.separator + f;
            System.out.println("Loading events file: " + f);

            EventList e = loadEvents(f);
            if (e != null) {
                eventDB.put(e.getUsername(), e);
            }
        }        
    }


    private static EventList loadEvents(String eventFile) {

        File f = new File(eventFile);

        // File should always exist!
        if (!f.exists()) {
            return null;
        }

        try {
            // Create XML document parser
            DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            Document data = db.parse(f);

            Node root = data.getFirstChild();
            if (root == null) return null;

            NamedNodeMap rootAttrs = root.getAttributes();
            String username = rootAttrs.getNamedItem("username").getNodeValue();

            System.out.println("---Loading events for: " + username);

            // Using existing EventList because other users may have invited
            // this user to events, which will already be present in this user's
            // EventList
            EventList events = getEvents(username);


            // Parse each <event> tag
            NodeList eventNodes = data.getElementsByTagName("event");
            for (int i = 0; i < eventNodes.getLength(); i++) {

                Node node = eventNodes.item(i);
                NamedNodeMap attrs = node.getAttributes();
                
                String uuid = attrs.getNamedItem("id").getNodeValue();
                Event e = new Event(username, uuid);
                e.setTitle(attrs.getNamedItem("title").getNodeValue());

                String startDate = attrs.getNamedItem("startdate").getNodeValue();
                startDate = startDate + " " +
                        attrs.getNamedItem("starttime").getNodeValue();
                e.setStartDate(startDate);

                String endDate = attrs.getNamedItem("enddate").getNodeValue();
                endDate = endDate + " " + attrs.getNamedItem("endtime").getNodeValue();
                e.setEndDate(endDate);

                // Get event description and <participant> tags
                e.setDescription("");
                NodeList children = node.getChildNodes();
                for (int j = 0; j < children.getLength(); j++) {
                    Node n = children.item(j);
                    if(n.getNodeType() == Node.TEXT_NODE) {
                        // Node contains event description
                        if(e.getDescription() == null || e.getDescription().trim().equals("")) {
                            e.setDescription(n.getNodeValue());
                        }
                    } else if (n.getNodeType() == Node.ELEMENT_NODE) {
                        // Entity node must be of type participant
                        if(!n.getNodeName().equals("participant")) {
                            continue;
                        }
                        NamedNodeMap participantAttrs = n.getAttributes();
                        String participantName = participantAttrs.getNamedItem("username").getNodeValue();
                        String participantStatus = participantAttrs.getNamedItem("status").getNodeValue();
                        if (participantStatus.equals("requested")) {
                            e.setParticipantStatus(participantName, Event.REQUESTED);
                        } else if (participantStatus.equals("confirmed")) {
                            e.setParticipantStatus(participantName, Event.CONFIRMED);
                        } else if (participantStatus.equals("declined")) {
                            e.setParticipantStatus(participantName, Event.DECLINED);
                        }

                        // Add event to participant's event list
                        getEvents(participantName).addAsParticipant(e);
                        
                    }
                }
                getEvents(username).addAsParticipant(e);

            }

            System.out.println("---Loaded " + events.getAllEvents().size() + 
                    " event(s)");
            return events;

        } catch (SAXException ex) {
            Logger.getLogger(EventsDB.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(EventsDB.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ParserConfigurationException ex) {
            Logger.getLogger(EventsDB.class.getName()).log(Level.SEVERE, null, ex);
        }

        return null;

    }


    /**
     * Gets the list of events for a particular user.  If the user has no event
     * list yet, a new empty event list is created for them.
     * @param username the user to retrieve the EventList of
     * @return the user's EventList
     */
    public static EventList getEvents(String username) {
        if (!eventDB.containsKey(username)) {
            EventList e = new EventList(username);
            eventDB.put(username, e);
        }

        return eventDB.get(username);
    }

    /**
     * Writes the given user's EventList to an XML file for backup
     * @param username the user whose EventList we want to save
     */
    public static synchronized void writeEventFile(String username) {

        Document data;
        DocumentBuilder db;
        try {
            // Create XML document parser
            db = DocumentBuilderFactory.newInstance().newDocumentBuilder();

            // create new data file
            data = db.newDocument();
            Element eventRoot = data.createElement("events");
            eventRoot.setAttribute("username", username);
            data.appendChild(eventRoot);

            // Add each event to the document
            EventList eList = getEvents(username);
            for (Event e: eList.getAllEvents()) {
                if (!e.getCreator().equals(username)) {
                    // Only write events that this user has created
                    continue;
                }

                Element event = data.createElement("event");
                event.setAttribute("id", e.getUUID());
                event.setAttribute("startdate", e.getStartDate("yyyy-MM-dd"));
                event.setAttribute("starttime", e.getStartDate("H:mm"));
                event.setAttribute("enddate", e.getEndDate("yyyy-MM-dd"));
                event.setAttribute("endtime", e.getEndDate("H:mm"));
                event.setAttribute("title", e.getTitle());

                Text desc = data.createTextNode(e.getDescription());
                event.appendChild(desc);

                // Add <participant> tags
                for (String pUsername : e.getParticipants()) {
                    Element participant = data.createElement("participant");
                    participant.setAttribute("username", pUsername);
                    participant.setAttribute("status", e.getParticipantStatus(pUsername));
                    event.appendChild(participant);
                }

                data.getFirstChild().appendChild(event);

            }

            // Write new document tree to file
            String eventFile = getFilenameFromUsername(username);
            File f = new File(eventFile);
            Transformer decepticon = TransformerFactory.newInstance().newTransformer();
            decepticon.setOutputProperty(OutputKeys.INDENT, "yes");
            DOMSource source = new DOMSource(data);
            StreamResult result = new StreamResult(f);
            decepticon.transform(source, result);


        } catch (TransformerConfigurationException ex) {
            Logger.getLogger(EventsDB.class.getName()).log(Level.SEVERE, null, ex);
        } catch (TransformerException ex) {
            Logger.getLogger(EventsDB.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ParserConfigurationException ex) {
            Logger.getLogger(EventsDB.class.getName()).log(Level.SEVERE, null, ex);
        }

    }



    private static String getFilenameFromUsername(String username) {
        String filename = eventFilePath + username.replaceAll("@", "_") + ".xml";
        return filename;
    }

    
    static class XMLFileFilter implements java.io.FilenameFilter {

        public boolean accept(File dir, String name) {

            if (name.toLowerCase().endsWith(".xml")) {
                    return true;
            }
            return false;
        }
        
    }
}
